﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Opennav.MapGenerator.Map.Roads;
using System.IO;
using System.Diagnostics;
using Opennav.MapGenerator.Map.MapTiles;

namespace Opennav.MapGenerator.Map.Roads
{
    public class RoadSubfile
    {
        private List<Road> _roadNetwork = new List<Road>();
        private int _offset = -1;

        public int Offset
        {
            get
            {
                Debug.Assert(_offset >= 0);
                return _offset;
            }
            set
            {
                this._offset = value;
            }
        }

        public IList<Road> RoadNetwork
        {
            get { return _roadNetwork; }
        }

        public void Optimize()
        {
            Console.WriteLine("Optimizing road network...");
            HashSet<RoadJunction> junctions = new HashSet<RoadJunction>();
            foreach (Road road in _roadNetwork)
            {
                foreach (RoadSegment segment in road.Segments)
                {
                    junctions.Add(segment.From);
                    junctions.Add(segment.To);
                }
            }
            List<Road> remove = new List<Road>();
            foreach (RoadJunction junction in junctions)
            {
                for (int i = 0; i < junction.Segments.Count; i++)
                {
                    for (int j = i + 1; j < junction.Segments.Count; j++)
                    {
                        Road a = junction.Segments[i].Road;
                        Road b = junction.Segments[j].Road;
                        if (a == b) continue;
                        if (a.City != b.City) continue;
                        if (a.Labels.Count != b.Labels.Count) continue;
                        if (a.Labels.Intersect(b.Labels).Count() != a.Labels.Count) continue;
                        foreach (RoadSegment segment in b.Segments)
                        {
                            segment.Road = a;
                            a.Segments.Add(segment);
                        }
                        b.Segments.Clear();
                        remove.Add(b);
                    }
                }
            }
            foreach (Road road in remove)
                _roadNetwork.Remove(road);
            Console.WriteLine("- optimization of road network removed " + remove.Count + " of " + (_roadNetwork.Count + remove.Count) + " roads.");
            int t = 0;
            foreach (RoadJunction junction in junctions)
            {
                if (junction.Segments.Count != 2) continue;
                RoadSegment a = junction.Segments[0];
                RoadSegment b = junction.Segments[1];
                if (a.Road != b.Road) continue;
                Road road = a.Road;
                if (a.From == b.From)
                    continue;
                if (a.To == b.To)
                    continue;
                if (a.From == b.To)
                {
                    RoadSegment temp = a;
                    a = b;
                    b = temp;
                }
                if (a.From == b.To) continue;
                Debug.Assert(a.To == b.From);
                if (a.Backward != b.Backward) continue;
                if (a.MaxSpeed != b.MaxSpeed) continue;
                if (a.OneWay != b.OneWay) continue;
                if (a.Toll != b.Toll) continue;
                if (a.Type != b.Type) continue;
                RoadSegment c = new RoadSegment();
                c.Backward = a.Backward;
                c.From = a.From;
                int length = a.Length + b.Length;
                Debug.Assert(length <= ushort.MaxValue);
                c.Length = (ushort)length;
                c.MaxSpeed = a.MaxSpeed;
                c.OneWay = a.OneWay;
                c.Road = a.Road;
                c.To = b.To;
                c.Toll = a.Toll;
                c.Type = a.Type;
                foreach (MapLine line in a.MapLines)
                {
                    line.RoadSegment = c;
                    c.MapLines.Add(line);
                }
                foreach (MapLine line in b.MapLines)
                {
                    line.RoadSegment = c;
                    c.MapLines.Add(line);
                }
                road.Segments.Remove(a);
                road.Segments.Remove(b);
                road.Segments.Add(c);
                a.From.Segments.Remove(a);
                a.From.Segments.Add(c);
                b.To.Segments.Remove(b);
                b.To.Segments.Add(c);

                t++;
            }
            Console.WriteLine("- optimization of road network removed " + t + " segments.");
        }

        public void Preprocess()
        {
            for (int i = 0; i < _roadNetwork.Count; i++)
                _roadNetwork[i].Id = i;
        }

        public void Write(Stream stream)
        {
            foreach (Road road in _roadNetwork)
            {
                road.Segments.Last().Last = true;
            }

            int ak = 0, aj = 0;
            foreach (Road road in _roadNetwork)
            {
                foreach (RoadSegment segment in road.Segments)
                {
                    if (segment.MapLines.Count == 0)
                        ak++;
                    else
                        aj++;
                }
            }

            int indexTableOffset = 0;
            int roadTableOffset = 0;
            int segmentTableOffset = 0;
            int junctionTableOffset = 0;
            int offset = (int)stream.Position;
            BinaryWriter writer = new BinaryWriter(stream);
            writer.Write(indexTableOffset);
            writer.Write((byte)(_roadNetwork.Count & 0xff));
            writer.Write((ushort)(_roadNetwork.Count >> 8));
            writer.Write(roadTableOffset);
            writer.Write(segmentTableOffset);
            writer.Write(junctionTableOffset);
            indexTableOffset = (int)stream.Position;
            stream.Seek(_roadNetwork.Count * sizeof(int), SeekOrigin.Current);
            HashSet<RoadSegment> segments = new HashSet<RoadSegment>();
            roadTableOffset = (int)stream.Position;
            foreach (Road road in _roadNetwork)
            {
                int roadOffset = (int)stream.Position - roadTableOffset;
                road.Offset = roadOffset;
                road.Write1(stream);
                foreach (RoadSegment segment in road.Segments)
                    segments.Add(segment);
            }
            HashSet<RoadJunction> junctions = new HashSet<RoadJunction>();
            segmentTableOffset = (int)stream.Position;
            foreach (RoadSegment segment in segments)
            {
                int segmentOffset = (int)stream.Position - segmentTableOffset;
                segment.Offset = segmentOffset;
                segment.Write1(stream);
                junctions.Add(segment.From);
                junctions.Add(segment.To);
            }
            junctionTableOffset = (int)stream.Position;
            foreach (RoadJunction junction in junctions)
            {
                int junctionOffset = (int)stream.Position - junctionTableOffset;
                junction.Offset = junctionOffset;
                junction.Write(stream);
            }
            int endOffset = (int)stream.Position;
            foreach (Road road in _roadNetwork)
            {
                stream.Seek(roadTableOffset + road.Offset, SeekOrigin.Begin);
                road.Write2(stream);
            }
            foreach (RoadSegment segment in segments)
            {
                stream.Seek(segmentTableOffset + segment.Offset, SeekOrigin.Begin);
                segment.Write2(stream);
            }
            foreach (Road road in _roadNetwork)
            {
                stream.Seek(indexTableOffset + road.Id * sizeof(int), SeekOrigin.Begin);
                road.WriteIndex(stream);
            }
            stream.Seek(offset, SeekOrigin.Begin);
            writer.Write(indexTableOffset - offset);
            writer.Write((byte)(_roadNetwork.Count & 0xff));
            writer.Write((ushort)(_roadNetwork.Count >> 8));
            writer.Write(roadTableOffset - offset);
            writer.Write(segmentTableOffset - offset);
            writer.Write(junctionTableOffset - offset);
            stream.Seek(endOffset, SeekOrigin.Begin);
        }
    }
}
